// parser 相关

// 定义状态机的状态
const State = {
  initial: 1, // 初始状态
  tagOpen: 2, // 标签开始状态
  tagName: 3, // 标签名称状态
  text: 4, // 文本状态
  tagEnd: 5, // 结束标签状态
  tagEndName: 6, // 结束标签名称状态
}

function isAlpha (char) {
  return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z');
}

// 接收模板字符串作为参数，并将模板切割为 Token 返回

function tokenize (str) {
  // 状态机当前的状态：初始状态
  let currentState = State.initial;

  // 用于缓存 字符
  const chars = []

  // 用于缓存 token 类型和值 
  const tokens = []

  while (str) {
    const char = str[0];
    switch (currentState) {
      case State.initial:
        if (char === '<') {
          currentState = State.tagOpen;
          str = str.slice(1);
        } else if (isAlpha(char)) {
          currentState = State.text;
          chars.push(char);
          str = str.slice(1);
        }
        break;

      case State.tagOpen:
        if (isAlpha(char)) {
          currentState = State.tagName;
          chars.push(char);
          str = str.slice(1);
        } else if (char === '/') {
          currentState = State.tagEnd;
          str = str.slice(1);
        }
        break;

      case State.tagName:
        if (char === '>') {
          currentState = State.initial;
          // 创建标签Token 存入Tokens
          tokens.push({
            type: 'tag',
            name: chars.join(''),
          });
          // 清空 chars
          chars.length = 0;
          str = str.slice(1);
        } else if (isAlpha(char)) {
          chars.push(char);
          str = str.slice(1);
        }
        break;

      case State.text:
        if (isAlpha(char)) {
          // 遇到字母状态不改变，
          chars.push(char);
          str = str.slice(1);
        } else if (char === '<') {
          currentState = State.tagOpen;

          tokens.push({
            type: 'text',
            content: chars.join(''),
          });
          chars.length = 0;
          str = str.slice(1);
        }
        break;

      case State.tagEnd:
        if (isAlpha(char)) {
          currentState = State.tagEndName;
          chars.push(char);
          str = str.slice(1);
        }
        break;

      case State.tagEndName:
        if (isAlpha(char)) {
          chars.push(char);
          str = str.slice(1);
        } else if (char === '>') {
          currentState = State.initial;
          tokens.push({
            type: 'tagEnd',
            name: chars.join(''),
          });
          chars.length = 0;
          str = str.slice(1);
        }
        break;
    }
  }
  return tokens;
}

const tokens = tokenize(`<div>hello</div>`);
// console.log('%c [ tokens ]-116', 'font-size:13px; background:pink; color:#bf2c9f;', tokens)

const tokens1 = tokenize(`<div><p>Vue</p><p>Template</p></div>`)
// console.log('[38;5;196m [ tokens1 ]-119 [0m', tokens1)


// Tokens 类型
const TAG_TYPE = {
  tag: 'tag',
  text: 'text',
  tagEnd: 'tagEnd'
}



// AST 抽象语法树转化
function parse (str) {
  // 对模板进行标记化，获取tokens
  const tokens = tokenize(str)

  // 创建根节点
  const Root = {
    type: 'Root',
    children: [],
  }

  // 创建elementStack栈 初始化 只有 根节点
  const elementStack = [Root]

  while (tokens.length) {
    // 获取栈顶元素 作为 父节点 parent 
    const parent = elementStack[elementStack.length - 1]

    const t = tokens[0]

    switch (t.type) {
      case TAG_TYPE.tag:
        const element = {
          type: 'Element',
          tag: t.name,
          children: [],
        }
        // 添加到父节点的children中
        parent.children.push(element)
        // 压栈
        elementStack.push(element)
        break;

      case TAG_TYPE.text:
        const textNode = {
          type: 'Text',
          content: t.content,
        }
        parent.children.push(textNode)
        break;

      case TAG_TYPE.tagEnd:
        // 结束 则把 栈顶元素弹出
        elementStack.pop()
        break;
    }

    // 消费掉已经遍历过的token
    tokens.shift()

  }

  return Root
}

const templateStr = '<div><p>Vue</p><p>Template</p></div>'

const AST = parse(templateStr)
// console.log('[90m [ AST ]-189 [0m', JSON.stringify(AST, null, 2))




// dump 函数 用于打印AST

function dump (node, indent = 0) {
  // 节点类型
  const type = node.type

  const desc = type === 'Root' ? '' : (
    type === 'Element' ? node.tag : node.content
  )

  console.log('[38;2;0;255;0m [  ]-204 [0m', `${'-'.repeat(indent)}${type}:${desc}`)

  if (node.children) {
    node.children.forEach(child => {
      dump(child, indent + 2)
    })
  }
}


// console.log('[30m [ AST ]-214 [0m',)
// console.log('[30m [ AST ]-214 [0m', dump(AST))

// console.log('',)
// console.log('',)
// console.log('',)

// 解耦写法 START
function traverseNode (ast, context) {
  // ast 本身就是 根节点
  // const currentNode = ast

  // 设置当前转换节点
  context.currentNode = ast

  // 记录退出阶段的回调函数数组 栈
  const exitFns = []


  // transforms 是一个数组 里面装着各种转换的函数
  const transforms = context.nodeTransforms
  for (let i = 0; i < transforms.length; i++) {
    const transform = transforms[i]

    //调用转换函数 需返回一个回调
    const onExit = transform(context.currentNode, context)
    if (onExit) {
      exitFns.push(onExit)
    }

    // 如果当前节点被删除了，则结束后续操作
    if (!context.currentNode) {
      return;
    }

  }



  // 递归处理每一个子节点
  const children = context.currentNode.children
  if (children) {
    // children.forEach(child => {
    //     traverseNode(child, context)
    // })

    for (let i = 0; i < children.length; i++) {
      // 递归前将当前转换节点作为 父节点 且记录 子节点索引
      context.parent = context.currentNode;
      context.childIndex = i;

      // 透传context
      traverseNode(children[i], context)
    }
  }


  // 倒叙 执行退出阶段的回调函数 
  // 栈， 先进后出

  let i = exitFns.length
  while (i--) {
    exitFns[i]()
  }
}

function transform (ast) {
  const context = {
    // 增加currentNode 存储当前转换的节点
    currentNode: null,
    //记录转换节点在父节点的索引
    childIndex: 0,
    // 存储转换节点的父节点
    parent: null,

    replaceNode (node) {
      // 找到当前节点在父元素的children中的位置  然后替换
      context.parent.children[context.childIndex] = node;

      // 替换完后 要把当前节点设置为新节点
      context.currentNode = node;
    },

    removeNode () {
      if (context.parent) {
        // 通过splice方法去删除当前节点
        context.parent.children.splice(context.childIndex, 1);
        // 删除完当前节点后 要把当前节点设置为null
        context.currentNode = null;
      }
    },


    nodeTransforms: [
      transformElement,
      transformText
    ]
  }

  traverseNode(ast, context)


  console.log('',)
  console.log('',)
  console.log('',)

  console.log('[38;5;196m [ transform 解耦写法 START]-269 [0m',)
  console.log('[38;5;196m [ transform 解耦写法 END]-269 [0m', dump(ast))
}

// 处理 p标签
function transformElement (node, context) {

  console.log('[38;5;220m [  ]-322 [0m', 'transformElement Enter');
  if (node.type === 'Element' && node.tag === 'p') {
    node.tag = 'h2'
  }

  return () => {

    console.log('[38;5;220m [  ]-327 [0m', 'transformElement Exit');
  }
}

// 处理文本内容
function transformText (node, context) {

  console.log('[48;2;255;0;255m [  ]-342 [0m', 'transformText Enter');

  if (node.type === 'Text') {
    // node.content = node.content.repeat(2)
    // // 替换文本节点
    // context.replaceNode({
    //   type: 'Element',
    //   tag: 'span',
    // })

    // 删除文本节点
    context.removeNode()
  }

  return () => {
    console.log('[48;2;255;0;255m [  ]-342 [0m', 'transformText Exit');
  }
}



transform(AST)

// 解耦写法  END